package com.lcz.licence.util;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lcz.licence.entity.LicenseVerifyParam;
import com.lcz.licence.serverinfo.CustomKeyStoreParam;
import de.schlichtherle.license.*;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

/**
 * @author lcz
 * @desc 许可证验证
 * @mail lcz_0130@163.com
 * @date 2025/07/23
 */

@Slf4j
public class LicenseVerify {

    private static final String CACHE_KEY_LICENSE_CHECK_RESULT = "license_check_result";
    //存储证书校验名称
    private static final String LICENSE_CHECK_TIME_ENCRYPTED_KEY = "lcz_last_verified_time";
    // AES密钥 16位密钥
    private static final String AES_KEY = "LczLicenseKey69.";

    private static final Cache<Object, Object> cache = Caffeine.newBuilder()
            // 初始数量
            .initialCapacity(10)
            // 最大条数
            .maximumSize(10)
            // PS：expireAfterWrite和expireAfterAccess同时存在时，以expireAfterWrite为准。
            // 最后一次写操作后经过指定时间过期
            .expireAfterWrite(3, TimeUnit.MINUTES)
            // 监听缓存被移除
            .removalListener((key, val, removalCause) -> {
            })
            // 记录命中
            .recordStats()
            .build();

    /**
     * 安装License证书
     */
    public synchronized void install(LicenseVerifyParam param) {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
            licenseManager.uninstall();
            LicenseContent result = licenseManager.install(FileUtils.getFile(param.getLicensePath()));
            log.info("证书安装成功，证书有效期：{} - {}", format.format(result.getNotBefore()), format.format(result.getNotAfter()));
        } catch (Exception e) {
            log.error("证书安装失败！", e);
            throw new RuntimeException("证书安装失败，服务终止", e);
        }
    }

    /**
     * 校验License证书
     */
    public boolean verify() {
        log.debug("开始检查系统时间是否回退");
        if (!checkSystemTimeRollback()) {
            log.error("系统时间被回退，证书验证失败");
            return false;
        }

        Object cacheVal = cache.getIfPresent(CACHE_KEY_LICENSE_CHECK_RESULT);
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (cacheVal != null) {
            log.debug("读取到缓存数据");
            LicenseContent licenseContent = (LicenseContent) cacheVal;
            log.debug("证书校验通过，证书有效期：{} - {}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()));
            return true;
        }
        LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
        // 校验证书
        try {
            LicenseContent licenseContent = licenseManager.verify();
            cache.put(CACHE_KEY_LICENSE_CHECK_RESULT, licenseContent);
            log.debug("证书校验通过，证书有效期：{} - {}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()));
            return true;
        } catch (Exception e) {
            log.error("证书校验失败！", e);
            return false;
        }
    }

    /**
     * 初始化证书生成参数
     */
    private LicenseParam initLicenseParam(LicenseVerifyParam param) {
        Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class,
                param.getPublicKeysStorePath(), param.getPublicAlias(), param.getStorePass(), null);
        return new DefaultLicenseParam(param.getSubject(), preferences, publicStoreParam, cipherParam);
    }

    /**
     * 检测系统时间是否回退
     */
    private boolean checkSystemTimeRollback() {
        Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
        long currentSystemTime = System.currentTimeMillis();
        // 解密存储的时间戳
        long storedTime = decryptTime(preferences.get(LICENSE_CHECK_TIME_ENCRYPTED_KEY, "0"));

        if (storedTime != 0 && currentSystemTime < storedTime) {
            log.error("系统时间被回退，请检查系统时间！");
            return false;
        }

        preferences.put(LICENSE_CHECK_TIME_ENCRYPTED_KEY, encryptTime(currentSystemTime));
        try {
            log.info("时间戳存储成功");
            preferences.flush();
        } catch (BackingStoreException e) {
            log.error("时间戳存储失败", e);
        }
        return true;
    }


    /**
     * 解密时间
     */
    private long decryptTime(String encryptedTime) {
        if ("0".equals(encryptedTime)) {
            return 0;
        }
        try {
            Cipher cipher = Cipher.getInstance("AES");
            SecretKeySpec secretKey = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);

            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedTime));
            return Long.parseLong(new String(decryptedBytes, StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error("时间戳解密失败", e);
            return 0;
        }
    }

    /**
     * 加密时间
     */
    private String encryptTime(long time) {
        try {
            Cipher cipher = Cipher.getInstance("AES");
            SecretKeySpec secretKey = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            byte[] encryptedBytes = cipher.doFinal(String.valueOf(time).getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            log.error("时间戳加密失败", e);
            return "0";
        }
    }
}
